﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Documents;
using System.Windows.Markup;
using Sora.HelpSystem.Package;
using Sora.HelpSystem;
using System.Windows;
using Sora.HelpSystem.Package.Pages;
using Sora.Data.Common.Streams;
using System.Xml;
using System.Xml.XPath;

namespace XamlImport
{
    class Program
    {
        [STAThread] /* because we are using wpf objects */
        static void Main(string[] args)
        {            
            Console.WriteLine("Sora HelpSystem Xaml Document Import");
            Console.WriteLine("Copyright (c) 2010 Karim Audrey LUCCIN");

            Environment.ExitCode = 1;

            if (args.Length < 0)
                ShowUsage();
            else
            {
                string src, dst;

                bool mergeMode = false;

                if (args[0].Equals("-m", StringComparison.OrdinalIgnoreCase))
                {
                    mergeMode = true;
                 
                    if (args.Length < 2)
                    {
                        ShowUsage();
                        return;
                    }
                }

                if (mergeMode)
                {
                    src = args[1];
                    dst = args.Length > 2 ? args[2] : Path.ChangeExtension(src, ".dshelp");
                }
                else
                {
                    src = args[0];
                    dst = args.Length > 1 ? args[1] : Path.ChangeExtension(src, ".dshelp");
                }

                if (!File.Exists(src))
                    Console.WriteLine(String.Format("File not found : {0}", src));
                else if (!mergeMode && File.Exists(dst))
                {
                    Console.WriteLine(String.Format("The file {0} is already present. Replace it ? (y/n)", dst));
                    if (!Console.ReadLine().Equals("y", StringComparison.OrdinalIgnoreCase))
                    {
                        Console.WriteLine("Operation aborted");
                        return;
                    }
                }

                string generatedTemporaryDirectory = null;
                try
                {

                    try
                    {
                        Console.WriteLine(String.Format("{0} --> {1}", src, dst));

                        generatedTemporaryDirectory = ExecuteImport(mergeMode, src, dst);

                        GC.Collect();
                        GC.WaitForPendingFinalizers();

                        Environment.ExitCode = 0;

                        Console.WriteLine("Success");

                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error");
                        Console.WriteLine(ex.ToString());
                    }
                }
                finally
                {
                    if (!String.IsNullOrEmpty(generatedTemporaryDirectory))
                    {
                        try
                        {
                            Directory.Delete(generatedTemporaryDirectory, true);
                        }
                        catch (IOException ex)
                        {
                            Console.WriteLine(String.Format("Temporary directory deletion failure : {0}", generatedTemporaryDirectory));
                            Console.WriteLine(ex.ToString());
                        }
                    }
                }
            }
        }


        /*
         * The document must be delimited in sections, each sections starting with
         * [DSHELPSECTION:]relative-uri
         * 
         * The relative-uri is the section uri, without the prefix dshelp://help.document/
         */
        const string DSHelpSectionPrefix = "[DSHELPSECTION:]";

        private static string ExecuteImport(bool mergeMode, string src, string dst)
        {
            string generatedTemporaryDirectory = null;

            using (FileStream fs = new FileStream(src, FileMode.Open, FileAccess.Read))
            {
                FlowDocument src_xaml = null;

                Console.WriteLine("Reading Source Document");

                if (Path.GetExtension(src) != null && Path.GetExtension(src).Equals(".docx", StringComparison.OrdinalIgnoreCase))
                {
                    var creator = new Synergist.WordToXamlLibrary.FlowDocumentCreator(new Synergist.WordToXamlLibrary.FlowDocumentOptions()
                    {
                        AutoColumnWidth = true,
                        AutoPagePadding = true,
                        IsColumnWidthFlexible = false,
                        XamlType = Synergist.WordToXamlLibrary.XamlType.WPF
                    });

                    generatedTemporaryDirectory = TemporaryStream.CreateTemporaryDirectory("XamlImport");

                    string wordDoc = Path.Combine(generatedTemporaryDirectory, Path.GetFileName(src));
                    File.Copy(src, wordDoc);

                    var xml_converted_content = creator.CreateXaml(wordDoc);

                    string basePath = Path.GetDirectoryName(Path.GetFullPath(wordDoc));
                    basePath += "\\";

                    ParserContext xamlContext = new ParserContext();
                    xamlContext.BaseUri = new Uri(basePath);

                    var corrector = new XmlDocument();
                    corrector.LoadXml(xml_converted_content);

                    foreach (XmlAttribute basedOnAttribute in corrector.SelectNodes("/*/*/*[@BasedOn]/@BasedOn"))
                    {
                        basedOnAttribute.Value = basedOnAttribute.Value.Trim();
                    }

                    foreach (XmlAttribute styleAttribute in corrector.SelectNodes("//@Style"))
                    {
                        styleAttribute.Value = styleAttribute.Value.Trim();
                    }

                    string xamlDoc = Path.Combine(generatedTemporaryDirectory, "XamlDocument.xaml");

                    corrector.Save(xamlDoc);

                    using (FileStream xaml_fs = new FileStream(xamlDoc, FileMode.Open, FileAccess.Read))
                    {
                        src_xaml = (FlowDocument)XamlReader.Load(xaml_fs, xamlContext);
                    }

                    //src_xaml = (FlowDocument)XamlReader.Load(XmlReader.Create(new StringReader(xml_converted_content))); //XamlReader.Load(, xamlContext);
                }
                else
                {
                    string basePath = Path.GetDirectoryName(Path.GetFullPath(src));
                    basePath += "\\";

                    ParserContext xamlContext = new ParserContext();
                    xamlContext.BaseUri = new Uri(basePath);

                    src_xaml = (FlowDocument)XamlReader.Load(fs, xamlContext);
                }

                Console.WriteLine("Creating Help Output");

                HelpDocument helpDocument = null;

                if (mergeMode)
                    helpDocument = new HelpDocument(dst, false, true);
                else
                    helpDocument = new HelpDocument();

                using (helpDocument)
                {
                    var helpProvider = new HelpDocumentProvider(helpDocument);

                    /* we are clearing the content of the user pages */
                    helpProvider.ResolveUri(new Uri(helpDocument.DocumentBaseUri + "/start"), true).FlowContent.Source.Blocks.Clear();
                    helpProvider.ResolveUri(new Uri(helpDocument.DocumentBaseUri + "/header"), true).FlowContent.Source.Blocks.Clear();

                    helpProvider.ResolveUri(new Uri(helpDocument.DocumentBaseUri + "/start"), true).Order = 1;

                    var currentHelpUri = String.Empty;
                    var currentPageTitle = String.Empty;

                    var clonnedDocument = CloneDocument(src_xaml);
                    Block[] clonnedBlocks = new Block[clonnedDocument.Blocks.Count];
                    clonnedDocument.Blocks.CopyTo(clonnedBlocks, 0);

                    int currentIndex = 0;
                    int pageOrder = 2;

                    foreach (Block block in src_xaml.Blocks)
                    {
                        Paragraph ps = block as Paragraph;

                        if (ps != null)
                        {
                            if (ps.Inlines.Count > 0)
                            {
                                var first_run = ps.Inlines.FirstInline as Run;
                                if (first_run != null)
                                {
                                    if (IsDSHelpSectionMarker(first_run))
                                    {
                                        var clonedPara = ((Paragraph)clonnedBlocks[currentIndex]);
                                        string first_text = BuildUriFromInlines(first_run, clonedPara);

                                        var helpUriParts = (helpDocument.DocumentBaseUri + "/" + first_text.Substring(first_text.IndexOf(']') + 1).Trim()).Split('|');

                                        currentHelpUri = FormatUri(helpUriParts[0]);

                                        if (helpUriParts.Length > 1)
                                            currentPageTitle = helpUriParts[1].Trim();
                                        else
                                            currentPageTitle = String.Empty;
                                    }
                                }
                            }
                        }

                        if (String.IsNullOrEmpty(currentHelpUri))
                        {
                            throw new InvalidDataException("A content was specified, but no section uri was specified");
                        }
                        else
                        {
                            Console.WriteLine(currentHelpUri);

                            var page = helpProvider.ResolveUri(new Uri(currentHelpUri), true);

                            page.FlowContent.Source.Blocks.Add(clonnedBlocks[currentIndex]);

                            if (!String.IsNullOrEmpty(currentPageTitle))
                            {
                                page.PageTitle = currentPageTitle;
                             
                                if (page.Order == 0)
                                {
                                    page.Order = pageOrder;
                                    pageOrder++;
                                }
                            }
                        }

                        currentIndex++;
                    }

                    Console.WriteLine("Generating Help Index");

                    helpProvider.SearchProvider.GenerateSearchIndex();

                    Console.WriteLine("Commit");

                    CommitDocuments(helpDocument);

                    helpDocument.SaveAs(dst, true);
                }
            }
            return generatedTemporaryDirectory;
        }

        private static bool IsDSHelpSectionMarker(Run current)
        {
            string text = String.Empty;

            while(current != null)
            {
                text += current.Text;
                current = current.NextInline as Run;

                /* because sometimes, the converter splits contiguous texts in several parts,
                 * and includes some spaces ... why ?!
                 */
                var trimmed = text.Trim().Replace(" ",""); 

                if(trimmed.Length >= DSHelpSectionPrefix.Length)
                {
                    return trimmed.StartsWith(DSHelpSectionPrefix, StringComparison.OrdinalIgnoreCase);
                }
            }

            return false;
        }

        private static string FormatUri(string str_uri)
        {
            if (String.IsNullOrEmpty(str_uri))
                return String.Empty;
            else
            {
                var parts = str_uri.Split('/');

                string res = String.Empty;

                foreach (var part in parts)
                {
                    res += part.ToLowerInvariant().Trim().Replace(' ', '-') + "/";
                }

                return res;
            }
        }

        private static string BuildUriFromInlines(Run current, Paragraph para)
        {
            string result = String.Empty;
            
            while (current != null)
            {
                result += current.Text;

                current = current.NextInline as Run;

                para.Inlines.Remove(para.Inlines.FirstInline);
            }

            return result;
        }

        private static void CommitDocuments(HelpDocument helpDocument)
        {
            CommitContent(helpDocument.HelpContent.Root);
        }

        private static void CommitContent(HelpPage page)
        {
            page.FlowContent.Commit();

            foreach (var child in page.Children)
            {
                CommitContent(child);
            }
        }

        private static FlowDocument CloneDocument(FlowDocument doc)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                TextRange range = new TextRange(doc.ContentStart, doc.ContentEnd);
                range.Save(ms, DataFormats.XamlPackage);

                ms.Position = 0;

                FlowDocument res = new FlowDocument();

                range = new TextRange(res.ContentStart, res.ContentEnd);
                range.Load(ms, DataFormats.XamlPackage);

                return res;
            }
        }

        private static void ShowUsage()
        {
            Console.WriteLine("Import word document generated with WordToXaml Converter");
            Console.WriteLine("WordToXaml Converter can be obtained at http://wordtoxaml.codeplex.com/");
            Console.WriteLine("Usage : XamlImport [-m] src_file [dst_file]");
            Console.WriteLine("Use -m to merge the output to dst_file");
        }
    }
}
